名域 ts {
    导出 函数 查找配置文件(搜索路径: 文字, 文件存在: (文件名: 文字) => 真假, 配置名称 = 'kconfig.json'): 文字 | 未定 {
        返回 循环每个祖先目录(搜索路径, 父级目录 => {
            常量 文件名 = 合并路径(父级目录, 配置名称);
            返回 文件存在(文件名) ? 文件名 : 未定;
        });
    }

    /* @internal */
    导出 函数 计算文件名从公共路径(名称组: 文字[], 当前目录: 文字, 获取规范文件名称: 获取规范文件名称_): 文字 {
        变量 更改的路径组件组: 文字[] | 未定;
        常量 临时结果 = 循环执行(名称组, 源文件 => {
            // Each 文件 contributes into common source 文件 路径
            常量 文件路径组件组 = 获取规范化路径组件(源文件, 当前目录);
            文件路径组件组.弹出(); // The base 文件 名称 is not part of the common directory 路径

            如果 (!更改的路径组件组) {
                // 首个 文件
                更改的路径组件组 = 文件路径组件组;
                返回;
            }

            常量 最小长度 = 数学_.min(更改的路径组件组.长度, 文件路径组件组.长度);
            循环 (变量 i = 0; i < 最小长度; i++) {
                如果 (获取规范文件名称(更改的路径组件组[i]) !== 获取规范文件名称(文件路径组件组[i])) {
                    如果 (i === 0) {
                        // 失败的_ to find any common 路径 component
                        返回 为真;
                    }

                    // New common 路径 found that is 0 -> i-1
                    更改的路径组件组.长度 = i;
                    跳出;
                }
            }

            // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents
            如果 (文件路径组件组.长度 < 更改的路径组件组.长度) {
                更改的路径组件组.长度 = 文件路径组件组.长度;
            }
        });

        // A common 路径 can not be found when paths 跨度 multiple drives on windows, for example
        如果 (临时结果) {
            返回 '';
        }

        如果 (!更改的路径组件组) { // Can happen when all input files are .d.ts files
            返回 当前目录;
        }

        返回 获取路径从路径组件组(更改的路径组件组);
    }

    接口 输出指纹_ {
        哈希代码: 文字;
        字节顺序标记_: 真假;
        修改时间: 日期_;
    }

    导出 函数 创建编译主机(选项组: 编译选项_, 设置父节点?: 真假): 编译主机_ {
        常量 现有目录词典 = 创建词典<真假>();

        函数 获取规范文件名(文件名: 文字): 文字 {
            // if underlying system can distinguish between two files whose names differs only in cases then 文件 名称 already in canonical form.
            // otherwise use toLowerCase as a canonical form.
            返回 sys.使用区分大小写文件名 ? 文件名 : 文件名.转为小写();
        }

        函数 获取源文件(文件名: 文字, 正在错误?: (消息: 文字) => 无值): 源文件_ | 未定 {
            变量 文本: 文字 | 未定;
            尝试 {
                性能.标记('IO读之前');
                文本 = sys.读文件(文件名, 选项组!.字符);
                性能.标记('IO读之后');
                性能.度量('I/O 读', 'IO读之前', 'IO读之后');
            }
            捕获 (e) {
                如果 (正在错误) {
                    正在错误(e.消息);
                }
                文本 = '';
            }

            返回 文本 !== 未定 ? 创建源文件(文件名, 文本, 选项组, 设置父节点) : 未定;
        }

        函数 目录存在(目录路径: 文字): 真假 {
            如果 (现有目录词典.具有(目录路径)) {
                返回 为真;
            }
            如果 (sys.目录存在(目录路径)) {
                现有目录词典.设置(目录路径, 为真);
                返回 为真;
            }
            返回 为假;
        }

        函数 确保目录存在(目录路径: 文字) {
            如果 (目录路径.长度 > 获取根长度(目录路径) && !目录存在(目录路径)) {
                常量 父目录 = 获取目录路径(目录路径);
                确保目录存在(父目录);
                sys.创建目录(目录路径);
            }
        }

        变量 输出指纹组: 词典_<输出指纹_>;

        函数 写文件如果更新(文件名: 文字, 数据: 文字, 写入字节序标记: 真假): 无值 {
            如果 (!输出指纹组) {
                输出指纹组 = 创建词典<输出指纹_>();
            }

            常量 哈希代码 = sys.创建哈希!(数据);
            常量 修改时间之前 = sys.获取修改时间!(文件名);

            如果 (修改时间之前) {
                常量 指纹 = 输出指纹组.获取(文件名);
                // If output has not been changed, and the 文件 has no external modification
                如果 (指纹 &&
                    指纹.字节顺序标记_ === 写入字节序标记 &&
                    指纹.哈希代码 === 哈希代码 &&
                    指纹.修改时间.获取时间() === 修改时间之前.获取时间()) {
                    返回;
                }
            }

            sys.写文件(文件名, 数据, 写入字节序标记);

            常量 修改时间之后 = sys.获取修改时间!(文件名);

            输出指纹组.设置(文件名, {
                哈希代码: 哈希代码,
                字节顺序标记_: 写入字节序标记,
                修改时间: 修改时间之后
            });
        }

        函数 写文件(文件名: 文字, 数据: 文字, 写入字节序标记: 真假, 正在错误?: (消息: 文字) => 无值) {
            尝试 {
                性能.标记('IO写之前');
                确保目录存在(获取目录路径(正规化路径(文件名)));

                如果 (是设置监控(选项组) && sys.创建哈希 && sys.获取修改时间) {
                    写文件如果更新(文件名, 数据, 写入字节序标记);
                }
                否则 {
                    sys.写文件(文件名, 数据, 写入字节序标记);
                }

                性能.标记('IO写之后');
                性能.度量('I/O 写', 'IO写之前', 'IO写之后');
            }
            捕获 (e) {
                如果 (正在错误) {
                    正在错误(e.消息);
                }
            }
        }

        函数 获取默认支持库位置(): 文字 {
            返回 获取目录路径(正规化路径(合并路径(当前目录, 'test/模块/lib/')))  // 获取目录路径(正规化路径(sys.获取正执行的文件路径()));
        }

        常量 新行 = 获取新行字符(选项组);
        常量 真实路径 = sys.真实路径 && ((路径: 文字) => sys.真实路径!(路径));
        常量 当前目录 = sys.获取当前目录();

        常量 全局模块目录 = 正规化路径(合并路径(当前目录, 'test/模块'));
        常量 当前模块目录 = 正规化路径(合并路径(当前目录, 'test/模块'));
        常量 当前源码目录 = 正规化路径(合并路径(当前目录, 'test/源码'));
        常量 当前项目配置 = 正规化路径(合并路径(当前目录, 'test/klang.mod.json'));

        返回 {
            获取源文件,
            获取默认支持库位置,
            获取默认支持库文件名: () => 合并路径(获取默认支持库位置(), 获取默认支持库文件名()),
            写文件,
            获取当前目录: 记忆(() => sys.获取当前目录()),
            使用区分大小写文件名: () => sys.使用区分大小写文件名,
            获取规范文件名称: 获取规范文件名,
            获取新行: () => 新行,
            文件存在: 文件名 => sys.文件存在(文件名),
            读文件: 文件名 => sys.读文件(文件名),
            跟踪: (s: 文字) => sys.写出(s + 新行),
            目录存在: 目录名称 => sys.目录存在(目录名称),
            获取环境变量: 名称 => sys.获取环境变量 ? sys.获取环境变量(名称) : '',
            获取目录组: (路径: 文字) => sys.获取目录组(路径),
            读目录: (路径: 文字, 扩展组?: 只读数组_<文字>, 深度?: 数字): 文字[] => sys.读目录(路径, 扩展组, 深度),
            全局模块目录,
            当前模块目录,
            当前源码目录,
            当前项目配置,
            真实路径
        };
    }

    导出 函数 获取预输出诊断(程序: 程序_, 源文件?: 源文件_, 取消令牌?: 取消令牌_): 诊断_[] {
        常量 诊断组 = [
            ...程序.获取选项诊断信息(取消令牌),
            ...程序.获取语法诊断信息(源文件, 取消令牌),
            ...程序.获取全局诊断信息(取消令牌),
            ...程序.获取语义诊断信息(源文件, 取消令牌)
        ];

        添加范围(诊断组, 程序.获取声明诊断信息(源文件, 取消令牌));

        返回 排序和消除重复诊断信息(诊断组);
    }

    导出 接口 格式化诊断主机_ {
        获取当前目录(): 文字;
        获取规范文件名(文件名: 文字): 文字;
        获取新行(): 文字;
    }

    导出 函数 格式化诊断信息(诊断组: 只读数组_<诊断_>, 主机: 格式化诊断主机_): 文字 {
        变量 输出 = '';

        循环 (常量 诊断 属于 诊断组) {
            输出 += 格式化诊断(诊断, 主机);
        }
        返回 输出;
    }

    导出 函数 格式化诊断(诊断: 诊断_, 主机: 格式化诊断主机_): 文字 {
        常量 错误消息 = `${诊断类别名称(诊断)} KLANG${诊断.代码}: ${拼合诊断信息文本(诊断.消息文本, 主机.获取新行())}${主机.获取新行()}`;

        如果 (诊断.文件) {
            常量 { 行, 字符 } = 获取位置的行和字符(诊断.文件, 诊断.开始!);
            常量 文件名 = 诊断.文件.文件名;
            常量 相对文件名称 = 转换到相对路径(文件名, 主机.获取当前目录(), 文件名 => 主机.获取规范文件名(文件名));
            返回 `${相对文件名称}(${行 + 1},${字符 + 1}): ` + 错误消息;
        }

        返回 错误消息;
    }

    /** @internal */
    导出 枚举 前景颜色转义序列_ {
        Grey = '\u001b[90m',
        Red = '\u001b[91m',
        Yellow = '\u001b[93m',
        Blue = '\u001b[94m',
        Cyan = '\u001b[96m'
    }
    常量 装订器样式序列 = '\u001b[30;47m';
    常量 装订器分隔符 = ' ';
    常量 重置转义序列 = '\u001b[0m';
    常量 省略号 = '...';
    函数 获取级别格式化(级别: 诊断级别_): 文字 {
        假如 (级别) {
            若是 诊断级别_.错误_: 返回 前景颜色转义序列_.Red;
            若是 诊断级别_.警告_: 返回 前景颜色转义序列_.Yellow;
            若是 诊断级别_.建议_: 返回 调试_.失败('Should never get an Info diagnostic on the command line.');
            若是 诊断级别_.消息_: 返回 前景颜色转义序列_.Blue;
        }
    }

    /** @internal */
    导出 函数 格式化颜色和剩余(文本: 文字, 格式化风格: 文字) {
        返回 格式化风格 + 文本 + 重置转义序列;
    }

    函数 填充左(s: 文字, 长度: 数字) {
        判断 (s.长度 < 长度) {
            s = ' ' + s;
        }
        返回 s;
    }

    导出 函数 格式化诊断包括颜色和上下文(诊断组: 只读数组_<诊断_>, 主机: 格式化诊断主机_): 文字 {
        变量 输出 = '';
        循环 (常量 诊断 属于 诊断组) {
            变量 上下文 = '';
            如果 (诊断.文件) {
                常量 { 开始, _长度, 文件 } = 诊断;
                常量 { 行: 第一行, 字符: 第一行字符 } = 获取位置的行和字符(文件, 开始!);
                常量 { 行: 最后行, 字符: 最后行字符 } = 获取位置的行和字符(文件, 开始! + _长度!);
                常量 最后行在文件中 = 获取位置的行和字符(文件, 文件.文本.长度).行;
                常量 相对文件名称 = 主机 ? 转换到相对路径(文件.文件名, 主机.获取当前目录(), 文件名 => 主机.获取规范文件名(文件名)) : 文件.文件名;

                常量 超过五行 = (最后行 - 第一行) >= 4;
                变量 装订线宽度 = (最后行 + 1 + '').长度;
                如果 (超过五行) {
                    装订线宽度 = 数学_.max(省略号.长度, 装订线宽度);
                }

                循环 (变量 i = 第一行; i <= 最后行; i++) {
                    上下文 += 主机.获取新行();
                    // If the error spans over 5 lines, we'll only show the 首个 2 and 最后的 2 lines,
                    // so we'll skip ahead to the second-to-最后的 line.
                    如果 (超过五行 && 第一行 + 1 < i && i < 最后行 - 1) {
                        上下文 += 格式化颜色和剩余(填充左(省略号, 装订线宽度), 装订器样式序列) + 装订器分隔符 + 主机.获取新行();
                        i = 最后行 - 1;
                    }

                    常量 行开始 = 获取行和字符的位置(文件, i, 0);
                    常量 行结束 = i < 最后行在文件中 ? 获取行和字符的位置(文件, i + 1, 0) : 文件.文本.长度;
                    变量 行内容 = 文件.文本.分切(行开始, 行结束);
                    行内容 = 行内容.替换(/\s+$/g, '');  // trim from 尾位
                    行内容 = 行内容.替换('\t', ' ');    // convert tabs to single spaces

                    // Output the gutter and the actual contents of the line.
                    上下文 += 格式化颜色和剩余(填充左(i + 1 + '', 装订线宽度), 装订器样式序列) + 装订器分隔符;
                    上下文 += 行内容 + 主机.获取新行();

                    // Output the gutter and the error 跨度 for the line using tildes.
                    上下文 += 格式化颜色和剩余(填充左('', 装订线宽度), 装订器样式序列) + 装订器分隔符;
                    上下文 += 前景颜色转义序列_.Red;
                    如果 (i === 第一行) {
                        // If we're on the 最后的 line, then limit it to the 最后的 character of the 最后的 line.
                        // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no 尾位 position.
                        常量 行的最后字符 = i === 最后行 ? 最后行字符 : 未定;

                        上下文 += 行内容.分切(0, 第一行字符).替换(/\S/g, ' ');
                        上下文 += 行内容.分切(第一行字符, 行的最后字符).替换(/./g, '~');
                    }
                    否则 如果 (i === 最后行) {
                        上下文 += 行内容.分切(0, 最后行字符).替换(/./g, '~');
                    }
                    否则 {
                        // Squiggle the entire line.
                        上下文 += 行内容.替换(/./g, '~');
                    }
                    上下文 += 重置转义序列;
                }

                输出 += 格式化颜色和剩余(相对文件名称, 前景颜色转义序列_.Cyan);
                输出 += ':';
                输出 += 格式化颜色和剩余(`${第一行 + 1}`, 前景颜色转义序列_.Yellow);
                输出 += ':';
                输出 += 格式化颜色和剩余(`${第一行字符 + 1}`, 前景颜色转义序列_.Yellow);
                输出 += ' - ';
            }

            输出 += 格式化颜色和剩余(诊断类别名称(诊断), 获取级别格式化(诊断.级别));
            输出 += 格式化颜色和剩余(` TS${诊断.代码}: `, 前景颜色转义序列_.Grey);
            输出 += 拼合诊断信息文本(诊断.消息文本, 主机.获取新行());

            如果 (诊断.文件) {
                输出 += 主机.获取新行();
                输出 += 上下文;
            }

            输出 += 主机.获取新行();
        }
        返回 输出 + 主机.获取新行();
    }

    导出 函数 拼合诊断信息文本(消息文本: 文字 | 诊断信息链_, 新行: 文字): 文字 {
        如果 (是文字(消息文本)) {
            返回 消息文本;
        }
        否则 {
            变量 诊断链 = 消息文本;
            变量 结果 = '';

            变量 缩进 = 0;
            判断 (诊断链) {
                如果 (缩进) {
                    结果 += 新行;

                    循环 (变量 i = 0; i < 缩进; i++) {
                        结果 += '  ';
                    }
                }
                结果 += 诊断链.消息文本;
                缩进++;
                诊断链 = 诊断链.下个!;
            }

            返回 结果;
        }
    }

    函数 加载包括本地缓存<T>(名称组: 文字[], 包含文件: 文字, 外部依赖: 词典_<文字>, 加载器: (名称: 文字, 包含文件: 文字, 外部依赖: 词典_<文字>) => T): T[] {
        如果 (名称组.长度 === 0) {
            返回 [];
        }
        常量 决议结果: T[] = [];
        常量 缓存 = 创建词典<T>();
        循环 (常量 名称 属于 名称组) {
            变量 结果: T;
            如果 (缓存.具有(名称)) {
                结果 = 缓存.获取(名称)!;
            }
            否则 {
                缓存.设置(名称, 结果 = 加载器(名称, 包含文件, 外部依赖));
            }
            决议结果.压入(结果);
        }
        返回 决议结果;
    }

    接口 诊断缓存_ {
        每个文件?: 词典_<诊断_[]>;
        全部诊断?: 诊断_[];
    }

    /**
     * Determines if program structure is upto date or needs to be recreated
     */
    /* @internal */
    导出 函数 是程序最新数据(程序: 程序_ | 未定, 根文件名称组: 文字[], 新选项: 编译选项_,
        获取源版本: (路径: 路径_) => 文字 | 未定, 文件存在: (文件名: 文字) => 真假, 具有无效解析: 有已失效的解析_): 真假 {
        // If we haven't created a program yet or have changed automatic 类型 directives, then it is not up-to-date
        如果 (!程序) {
            返回 为假;
        }

        // If number of files in the program do not match, it is not up-to-date
        如果 (程序.获取根文件名称().长度 !== 根文件名称组.长度) {
            返回 为假;
        }

        // If any 文件 is not up-to-date, then the whole program is not up-to-date
        如果 (程序.获取源文件组().某些(源文件有更新)) {
            返回 为假;
        }

        // If any of the 失踪的 文件 paths are now created
        如果 (程序.获取失踪文件路径().某些(文件存在)) {
            返回 为假;
        }

        常量 当前选项 = 程序.获取编译选项();
        // If the compilation settings do no match, then the program is not up-to-date
        如果 (!比较数据对象(当前选项, 新选项)) {
            返回 为假;
        }

        返回 为真;

        函数 源文件有更新(源文件: 源文件_): 真假 {
            返回 源文件.版本 !== 获取源版本(源文件.路径) || 具有无效解析(源文件.路径);
        }
    }

    /**
     * Determined if source 文件 needs to be re-created even if its 文本 hasn't changed
     */
    函数 程序是否应创建新源文件组(程序: 程序_ | 未定, 新选项: 编译选项_) {
        // If any of these options change, we can't reuse old source 文件 even if version match
        // The change in options like these could 结果 in change in syntax tree change
        常量 旧选项 = 程序 && 程序.获取编译选项();
        返回 旧选项 === 新选项;
    }

    /**
     * Create a new 'Program' instance. A Program is an immutable collection of '源文件_'s and a 'CompilerOptions'
     * that represent a compilation unit.
     *
     * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and
     * triple-斜杠号_-reference-路径 directives transitively. '@类型组' and triple-斜杠号_-reference-类型组 are also pulled in.
     *
     * @param 根名称组 - A set of root files.
     * @param 选项组 - The compiler options which should be used.
     * @param 主机 - The host interacts with the underlying 文件 system.
     * @param 旧程序 - Reuses an old program structure.
     * @returns A 'Program' object.
     */
    导出 函数 创建程序(初始文件组: 只读数组_<文字> | 未定, 选项组: 编译选项_, 主机?: 编译主机_, 旧程序?: 程序_): 程序_ {
        变量 程序: 程序_;
        变量 本地模块组: 模块_[] = [];
        变量 外部模块组: 模块_[] = [];
        变量 文件组: 源文件_[] = [];
        变量 公共源目录: 文字;
        变量 诊断生成类型检查器!: 类型检查_;
        变量 无诊断类型检查器!: 类型检查_;
        变量 修改的文件路径组: 路径_[];

        常量 缓存的文件的语言诊断: 诊断缓存_ = {};

        变量 文件处理诊断 = 创建诊断集合();

        // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled.
        常量 源文件是从外部模块组搜索的 = 创建词典<真假>();

        性能.标记('程序之前');

        主机 = 主机! || 创建编译主机(选项组);

        变量 根名称组 = 初始文件组 && 初始文件组.长度 ? 初始文件组 : 过滤器(主机.读目录(正规化路径(合并路径(主机.获取当前目录(), 'test/源码/')), [扩展名_.K], 1), p => 扩展名是K和DK不含TestK(p))

        常量 获取默认支持库文件名称 = 记忆(() => 主机!.获取默认支持库文件名());
        常量 程序诊断 = 创建诊断集合();
        常量 当前目录 = 主机.获取当前目录();
        常量 支持的扩展组 = 获取支持的扩展();
        

        常量 依赖 = 读Json(正规化路径(合并路径(当前目录, 'test/klang.mod.json')), 主机).依赖 || {}
        常量 依赖模块目录 = 创建词典<文字>()
        如果 (依赖) {
            循环 (变量 k 位于 依赖) {
                变量 存在 = 具有属性(依赖, k)
                如果 (存在) {
                    变量 版本 = 依赖[k]
                    变量 键 = 正规化路径(k)
                    如果 (路径以分隔符结尾(键)) {
                        键 = 键.分切(0, -1)
                    }
                    依赖模块目录.设置(键, 正规化路径(合并路径(k, 版本)))
                }
            }
        }

        // Map storing if there is emit blocking diagnostics for given input
        //  常量 具有输出阻塞诊断 = 创建词典<真假>();

        变量 模块解析缓存: 模块解析缓存_;
        变量 解析模块名称平台: (模块名称组: 文字[], 包含文件: 文字, 重复使用的名称组?: 文字[]) => 解析的模块名称_[];
        常量 具有无效的解析 = 主机.具有失效解析 || 返回假;
        如果 (主机.解析模块名称组) {
            解析模块名称平台 = (模块名称组, 包含文件, 重复使用的名称组) => 主机!.解析模块名称组!(检查全部定义(模块名称组), 包含文件, 重复使用的名称组).映射(解析的 => 解析的!);
        }
        否则 {
            模块解析缓存 = 创建模块解析缓存(当前目录, x => 主机!.获取规范文件名称(x));
            常量 加载器 = (模块名称: 文字, 包含文件: 文字, 外部依赖: 词典_<文字>) => 解析模块名称(模块名称, 包含文件, 选项组, 主机!, 外部依赖, 模块解析缓存).模块目录路径!;
            解析模块名称平台 = (模块名称组, 包含文件) => 加载包括本地缓存(检查全部定义(模块名称组), 包含文件, 依赖模块目录, 加载器);
        }

        // Maps from a 源文件_'s `.路径` to the 名称 of the package it was imported with.
        变量 源文件转为包名称 = 创建词典<文字>();
        // See `sourceFileIsRedirectedTo`.
        变量 重定向目标集 = 创建词典<为真>();

        常量 文件组从名称 = 创建词典<源文件_ | 未定>();

        变量 失踪文件路径组: 只读数组_<路径_> | 未定;
        // stores 'filename -> 文件 association' ignoring case
        // used to track cases when two 文件 names differ only in casing
        常量 按名称排列忽略大小写 = 主机.使用区分大小写文件名() ? 创建词典<源文件_>() : 未定;

        常量 应创建新源文件 = 程序是否应创建新源文件组(旧程序, 选项组);
        常量 结构是重用 = 尝试重用旧程序中的结构();
        如果 (结构是重用 !== 结构被重用_.完全_) {
            循环执行(根名称组, 名称 => 进程根文件(名称, /*isDefaultLib*/ 为假));
            进程根文件(获取默认支持库文件名称(), /*isDefaultLib*/ 为真);
            失踪文件路径组 = 数组从(文件组从名称.键组(), p => <路径_>p).过滤器(p => !文件组从名称.获取(p));
        }

        调试_.断言(!!失踪文件路径组);

        // unconditionally set moduleResolutionCache to 未定 to avoid unnecessary leaks
        模块解析缓存 = 未定!;

        // Release any files we have acquired in the old program but are
        // not part of the new program.
        如果 (旧程序 && 主机.正在发布旧源文件) {
            常量 旧的源文件组 = 旧程序.获取源文件组();
            循环 (常量 旧的源文件 属于 旧的源文件组) {
                如果 (!获取源文件(旧的源文件.路径) || 应创建新源文件) {
                    主机.正在发布旧源文件(旧的源文件, 旧程序.获取编译选项());
                }
            }
        }

        // unconditionally set oldProgram to 未定 to prevent it from being captured in closure
        旧程序 = 未定;

        程序 = {
            获取根文件名称: () => 根名称组,
            获取源文件,
            获取源文件从路径,
            获取源文件组: () => 文件组,
            获取失踪文件路径: () => 失踪文件路径组!,
            获取编译选项: () => 选项组,
            获取语法诊断信息,
            获取选项诊断信息,
            获取全局诊断信息,
            获取语义诊断信息,
            获取声明诊断信息,
            获取类型检查,
            获取诊断生成类型检查器,
            获取公共源目录,
            输出,
            获取当前目录: () => 当前目录,
            获取节点数量: () => 获取诊断生成类型检查器().获取节点数量(),
            获取标识符数量: () => 获取诊断生成类型检查器().获取标识符数量(),
            获取符号数量: () => 获取诊断生成类型检查器().获取符号数量(),
            获取类型数量: () => 获取诊断生成类型检查器().获取类型数量(),
            获取文件处理诊断信息: () => 文件处理诊断,
            是来自外部库的源文件,
            丢弃诊断生成类型检查器,
            源文件转为包名称,
            重定向目标集,
            是输出的文件,
            获取本地模块组: () => 本地模块组,
            获取外部模块组: () => 外部模块组,
            依赖模块目录,
            当前源码目录: 主机.当前源码目录,
            当前模块目录: 主机.当前模块目录,
            全局模块目录: 主机.全局模块目录,
            当前项目配置: 主机.当前项目配置
        };
        性能.标记('程序之后');
        性能.度量('程序', '程序之前', '程序之后');

        返回 程序;

        函数 转为路径(文件名: 文字): 路径_ {
            返回 ts.转为路径(文件名, 当前目录, 获取规范文件名称);
        }

        函数 获取公共源目录() {
            如果 (公共源目录 === 未定) {
                常量 输出的文件组 = 过滤器(文件组, 文件 => 源文件可能已发出(文件, 是来自外部库的源文件));
                公共源目录 = 计算公共源目录(输出的文件组);
                如果 (公共源目录 && 公共源目录[公共源目录.长度 - 1] !== 目录分隔符) {
                    // Make sure directory 路径 ends with directory separator so this string can directly
                    // used to replace with '' to get the relative 路径 of the source 文件 and the relative 路径 doesn't
                    // 开始 with / making it rooted 路径
                    公共源目录 += 目录分隔符;
                }
            }
            返回 公共源目录;
        }

        接口 旧程序状态_ {
            程序: 程序_ | 未定;
            旧源文件: 源文件_ | 未定;
            /** The collection of paths modified *since* the old program. */
            修改的文件路径组: 路径_[];
        }

        函数 解析模块名称重用旧状态(模块名称组: 文字[], 包含文件: 文字, 文件: 源文件_, 旧程序状态: 旧程序状态_) {
            如果 (结构是重用 === 结构被重用_.非_) {
                // If the old program 状态 does not permit reusing resolutions and `文件` does not contain locally defined ambient modules,
                // the best we can do is fallback to the 默认 logic.
                返回 解析模块名称平台(模块名称组, 包含文件);
            }

            常量 旧源文件 = 旧程序状态.程序 && 旧程序状态.程序.获取源文件(包含文件);
            如果 (旧源文件 !== 文件 && 文件.解析的模块) {
                // `文件` was created for the new program.
                //
                // We only set `文件.resolvedModules` via work from the 当前 function,
                // so it is defined iff we already called the 当前 function on `文件`.
                // That call happened no later than the creation of the `文件` object,
                // which per above occured during the 当前 program creation.
                // Since we assume the filesystem does not change during program creation,
                // it is safe to reuse resolutions from the earlier call.
                常量 结果: 解析的模块名称_[] = [];
                循环 (常量 模块名称 属于 模块名称组) {
                    常量 解析模块 = 文件.解析的模块.获取(模块名称)!;
                    结果.压入(解析模块);
                }
                返回 结果;
            }
            // At this point, we know at least one of the following hold:
            // - 文件 has local 声明组 for ambient modules
            // - old program 状态 is available
            // With this information, we can infer some module resolutions without performing resolution.

            /** An ordered 列表 of module names for which we cannot recover the resolution. */
            变量 未知模块名称!: 文字[];
            /**
             * The indexing of 元素组 in this 列表 matches that of `moduleNames`.
             *
             * Before combining results, 结果[i] is in one of the following states:
             * * 未定: needs to be recomputed,
             * * predictedToResolveToAmbientModuleMarker: known to be an ambient module.
             * Needs to be reset to 未定 before returning,
             * * 解析模块全部_ instance: can be reused.
             */
            变量 结果: 解析的模块名称_[] | 未定;
            变量 重复使用的名称!: 文字[];
            /** A transient placeholder used to mark predicted resolution in the 结果 列表. */
            常量 预测到解析为环境模块标记: 解析的模块名称_ = <任意>{};

            循环 (变量 i = 0; i < 模块名称组.长度; i++) {
                常量 模块名称 = 模块名称组[i];
                // If the source 文件 is unchanged and doesnt have invalidated resolution, reuse the module resolutions
                如果 (文件 === 旧源文件 && !具有无效的解析(旧源文件.路径)) {
                    常量 旧解析模块 = 旧源文件 && 旧源文件.解析的模块.获取(模块名称);
                    如果 (旧解析模块) {
                        如果 (是跟踪启用(选项组, 主机!)) {
                            跟踪(主机!, 诊断信息_.对文件_1_重用旧程序中模块_0_的解析, 模块名称, 包含文件);
                        }
                        (结果 || (结果 = 新建 数组_(模块名称组.长度)))[i] = 旧解析模块;
                        (重复使用的名称 || (重复使用的名称 = [])).压入(模块名称);
                        继续;
                    }
                }
                // We know 模块名称 resolves to an ambient module provided that 模块名称:
                // - is in the 列表 of ambient modules locally declared in the 当前 source 文件.
                // - resolved to an ambient module in the old program whose declaration is in an unmodified 文件
                //   (so the same module declaration will land in the new program)
                常量 解析环境模块非修改文件 = 模块名称解析为未修改文件中的环境模块(模块名称, 旧程序状态);

                如果 (解析环境模块非修改文件) {
                    (结果 || (结果 = 新建 数组_(模块名称组.长度)))[i] = 预测到解析为环境模块标记;
                }
                否则 {
                    // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the 结果.
                    (未知模块名称 || (未知模块名称 = [])).压入(模块名称);
                }
            }

            常量 决议组 = 未知模块名称 && 未知模块名称.长度
                ? 解析模块名称平台(未知模块名称, 包含文件, 重复使用的名称)
                : 空数组;

            // Combine results of resolutions and predicted results
            如果 (!结果) {
                // There were no unresolved/ambient resolutions.
                调试_.断言(决议组.长度 === 模块名称组.长度);
                返回 决议组;
            }

            变量 j = 0;
            循环 (变量 i = 0; i < 结果.长度; i++) {
                如果 (结果[i]) {
                    // `结果[i]` is either a `解析模块全部_` or a marker.
                    // If it is the former, we can leave it as is.
                    如果 (结果[i] === 预测到解析为环境模块标记) {
                        结果[i] = 未定!;
                    }
                }
                否则 {
                    结果[i] = 决议组[j];
                    j++;
                }
            }
            调试_.断言(j === 决议组.长度);

            返回 结果;

            // If we change our policy of rechecking failed lookups on each program create,
            // we should adjust the value returned here.
            函数 模块名称解析为未修改文件中的环境模块(模块名称: 文字, 旧程序状态: 旧程序状态_): 真假 {
                常量 解析到文件 = 获取解析模块(旧程序状态.旧源文件!, 模块名称);
                常量 文件组 = 主机!.读目录(解析到文件!.解析的模块目录路径, [扩展名_.K], 1)
                循环执行(文件组, f => {
                    常量 解析文件 = 解析到文件 && 旧程序状态.程序 && 旧程序状态.程序.获取源文件(f);
                    如果 (解析到文件 && 解析文件) {
                        // In the old program, we resolved to an ambient module that was in the same
                        //   place as we expected to find an actual module 文件.
                        // We actually need to return 'false' here even though this seems like a 'true' case
                        //   because the normal module resolution algorithm will find this anyway.
                        返回 为假;
                    }

                })
                返回 为真;
            }
        }

        函数 尝试重用旧程序中的结构(): 结构被重用_ {
            如果 (!旧程序) {
                返回 结构被重用_.非_;
            }

            // check 属性组 that can affect structure of the program or module resolution strategy
            // if any of these 属性组 has changed - structure cannot be reused
            常量 旧选项 = 旧程序.获取编译选项();
            如果 (更改影响模块解析(旧选项, 选项组)) {
                返回 旧程序.结构被重用 = 结构被重用_.非_;
            }

            调试_.断言(!(旧程序.结构被重用! & (结构被重用_.完全_ | 结构被重用_.安全模块_)));

            // there is an old program, check if we can reuse its structure
            常量 旧根名称 = 旧程序.获取根文件名称();

            如果 (!数组是相等的(旧根名称, 根名称组)) {
                返回 旧程序.结构被重用 = 结构被重用_.非_;
            }

            // check if program source files has changed in the way that can affect structure of the program
            常量 新源文件组: 源文件_[] = [];
            常量 文件路径组: 路径_[] = [];
            常量 修改的源文件: { 旧文件: 源文件_, 新源文件: 源文件_ }[] = [];
            旧程序.结构被重用 = 结构被重用_.完全_;

            // If the 失踪的 文件 paths are now present, it can change the progam structure,
            // and hence cant reuse the structure.
            // This is same as how we dont reuse the structure if one of the 文件 from old program is now 失踪的
            如果 (旧程序.获取失踪文件路径().某些(失踪文件路径 => 主机!.文件存在(失踪文件路径))) {
                返回 旧程序.结构被重用 = 结构被重用_.非_;
            }

            常量 旧源文件组 = 旧程序.获取源文件组();
            常量 枚举 看见包名称 { 存在, 修改 }
            常量 看见的包名称 = 创建词典<看见包名称>();

            循环 (常量 旧源文件 属于 旧源文件组) {
                变量 新源文件 = 主机!.获取源文件从路径 
                ? 主机!.获取源文件从路径(旧源文件.文件名, 旧源文件.路径,  /*onError*/ 未定, 应创建新源文件)
                    : 主机!.获取源文件(旧源文件.文件名,  /*onError*/ 未定, 应创建新源文件);

                如果 (!新源文件) {
                    返回 旧程序.结构被重用 = 结构被重用_.非_;
                }

                调试_.断言(!新源文件.重定向信息, '主机不应该从 `获取源文件` 获取到重定向文件');

                变量 文件已更改: 真假;
                如果 (旧源文件.重定向信息) {
                    // We got `新源文件` by 路径, so it is actually for the 未定向 文件.
                    // This lets us know if the 未定向 文件 has changed. If it has we should break the redirect.
                    如果 (新源文件 !== 旧源文件.重定向信息.未定向) {
                        // Underlying 文件 has changed. Might not redirect anymore. Must rebuild program.
                        返回 旧程序.结构被重用 = 结构被重用_.非_;
                    }
                    文件已更改 = 为假;
                    新源文件 = 旧源文件; // Use the redirect.
                }
                否则 如果 (旧程序.重定向目标集.具有(旧源文件.路径)) {
                    // If a redirected-to source 文件 changes, the redirect may be broken.
                    如果 (新源文件 !== 旧源文件) {
                        返回 旧程序.结构被重用 = 结构被重用_.非_;
                    }
                    文件已更改 = 为假;
                }
                否则 {
                    文件已更改 = 新源文件 !== 旧源文件;
                }

                新源文件.路径 = 旧源文件.路径;
                文件路径组.压入(新源文件.路径);

                常量 包名称 = 旧程序.源文件转为包名称.获取(旧源文件.路径);
                如果 (包名称 !== 未定) {
                    // If there are 2 different source files for the same package 名称 and at least one of them changes,
                    // they might become redirects. So we must rebuild the program.
                    常量 前一种 = 看见的包名称.获取(包名称);
                    常量 新种类 = 文件已更改 ? 看见包名称.修改 : 看见包名称.存在;
                    如果 ((前一种 !== 未定 && 新种类 === 看见包名称.修改) || 前一种 === 看见包名称.修改) {
                        返回 旧程序.结构被重用 = 结构被重用_.非_;
                    }
                    看见的包名称.设置(包名称, 新种类);
                }

                如果 (文件已更改) {
                    // check imports and module augmentations
                    收集外部模块引用(新源文件);
                    如果 (!数组是相等的(旧源文件.导入组, 新源文件.导入组, 模块名称是相等)) {
                        // imports has changed
                        旧程序.结构被重用 = 结构被重用_.安全模块_;
                    }
                    修改的源文件.压入({ 旧文件: 旧源文件, 新源文件: 新源文件 });
                }
                否则 如果 (具有无效的解析(旧源文件.路径)) {
                    // 'module/类型组' references could have changed
                    旧程序.结构被重用 = 结构被重用_.安全模块_;

                    // add 文件 to the modified 列表 so that we will resolve it later
                    修改的源文件.压入({ 旧文件: 旧源文件, 新源文件: 新源文件 });
                }

                // if 文件 has passed all checks it should be safe to reuse it
                新源文件组.压入(新源文件);
            }

            如果 (旧程序.结构被重用 !== 结构被重用_.完全_) {
                返回 旧程序.结构被重用;
            }

            修改的文件路径组 = 修改的源文件.映射(f => f.新源文件.路径);
            // try to verify results of module resolution
            循环 (常量 { 旧文件, 新源文件 } 属于 修改的源文件) {

                常量 新源文件路径 = 获取标准化绝对路径(新源文件.文件名, 当前目录);

                如果 (解析模块名称平台) {
                    常量 模块名称组 = 获取模块名称(新源文件);
                    常量 旧程序状态: 旧程序状态_ = { 程序: 旧程序, 旧源文件: 旧文件, 修改的文件路径组 };
                    常量 决议组 = 解析模块名称重用旧状态(模块名称组, 新源文件路径!, 新源文件, 旧程序状态);
                    // ensure that module resolution results are still correct
                    常量 决议已更改 = 有更改在决议组(模块名称组, 决议组, 旧文件.解析的模块, 模块是相等的);
                    如果 (决议已更改) {
                        旧程序.结构被重用 = 结构被重用_.安全模块_;
                        新源文件.解析的模块 = 压缩转为词典(模块名称组, 决议组);
                    }
                    否则 {
                        新源文件.解析的模块 = 旧文件.解析的模块;
                    }
                }
            }

            如果 (旧程序.结构被重用 !== 结构被重用_.完全_) {
                返回 旧程序.结构被重用;
            }

            如果 (主机!.具有更改诊断类型指令名称) {
                返回 旧程序.结构被重用 = 结构被重用_.安全模块_;
            }

            失踪文件路径组 = 旧程序.获取失踪文件路径();

            // update 文件名 -> 文件 mapping
            循环 (变量 i = 0; i < 新源文件组.长度; i++) {
                文件组从名称.设置(文件路径组[i], 新源文件组[i]);
                // Set the 文件 as found during n modules search if it was found that way in old progra,
                如果 (旧程序.是来自外部库的源文件(旧程序.获取源文件从路径(文件路径组[i])!)) {
                    源文件是从外部模块组搜索的.设置(文件路径组[i], 为真);
                }
            }

            文件组 = 新源文件组;
            文件处理诊断 = 旧程序.获取文件处理诊断信息();

            循环 (常量 修改 属于 修改的源文件) {
                文件处理诊断.重新附加文件诊断(修改.新源文件);
            }

            源文件转为包名称 = 旧程序.源文件转为包名称;
            重定向目标集 = 旧程序.重定向目标集;

            返回 旧程序.结构被重用 = 结构被重用_.完全_;
        }
        /*

        函数 获取输出主机(写文件回调?: 写文件回调_): 输出主机_ {
            返回 {
                获取规范文件名称,
                获取公共源目录: 程序.获取公共源目录,
                获取编译选项: 程序.获取编译选项,
                获取当前目录: () => 当前目录,
                获取新行: () => 主机!.获取新行(),
                获取源文件: 程序.获取源文件,
                获取源文件从路径: 程序.获取源文件从路径,
                获取源文件组: 程序.获取源文件组,
                是来自外部库的源文件,
                写文件: 写文件回调 || ((文件名, 数据, 写入字节序标记, 正在错误, 源文件组) => 主机!.写文件(文件名, 数据, 写入字节序标记, 正在错误, 源文件组)),
                是输出阻塞,
            };
        }
        */

        函数 是来自外部库的源文件(文件: 源文件_): 真假 {
            返回 源文件是从外部模块组搜索的.获取(文件.路径)!;
        }

        函数 获取诊断生成类型检查器() {
            返回 诊断生成类型检查器 || (诊断生成类型检查器 = 创建类型检查(程序, /*produceDiagnostics:*/ 为真));
        }

        函数 丢弃诊断生成类型检查器() {
            诊断生成类型检查器 = 未定!;
        }

        函数 获取类型检查() {
            返回 无诊断类型检查器 || (无诊断类型检查器 = 未定! 转为 类型检查_); //|| (无诊断类型检查器 = 创建类型检查(程序, /*produceDiagnostics:*/ 为假));
        }

        函数 输出(源文件?: 源文件_, 写文件回调?: 写文件回调_, 取消令牌?: 取消令牌_, 输出仅声明文件?: 真假, 转换器组?: 自定义转换器_): 输出结果_ {
            返回 运行包括取消令牌(() => 输出平台(程序, 源文件!, 写文件回调!, 取消令牌!, 输出仅声明文件, 转换器组))!;
        }

        /*
        函数 是输出阻塞(输出文件名称: 文字): 真假 {
            返回 具有输出阻塞诊断.具有(转为路径(输出文件名称));
        }
        */

        函数 输出平台(程序: 程序_, 源文件: 源文件_, 写文件回调: 写文件回调_, 取消令牌: 取消令牌_, 输出仅声明文件?: 真假, 自定义转换器?: 自定义转换器_): 输出结果_ | 未定 {
            变量 声明诊断: 只读数组_<诊断_> = [];

            常量 诊断组 = [
                ...程序.获取选项诊断信息(取消令牌),
                ...程序.获取语法诊断信息(源文件, 取消令牌),
                ...程序.获取全局诊断信息(取消令牌),
                ...程序.获取语义诊断信息(源文件, 取消令牌)
            ];

            如果 (诊断组.长度 === 0) {
                声明诊断 = 程序.获取声明诊断信息(/*源文件*/ 未定, 取消令牌);
            }

            如果 (诊断组.长度 > 0 || 声明诊断.长度 > 0) {
                返回 {
                    诊断组: 组合(诊断组, 声明诊断),
                    源映射: 未定!,
                    输出文件: 未定!,
                    输出跳过: 为真
                };
            }

            // Create the emit resolver outside of the 'emitTime' tracking code below.  That way
            // any cost associated with it (like 类型 checking) are appropriate associated with
            // the 类型-checking counter.
            //
            // If the -out option is specified, we should not pass the source 文件 to getEmitResolver.
            // This is because in the -out scenario all files need to be emitted, and therefore all
            // files need to be 类型 checked. And the way to specify that all files need to be 类型
            // checked is to not pass the 文件 to getEmitResolver.
            常量 输出解析器 = 获取诊断生成类型检查器().获取输出解析器(源文件, 取消令牌);

            性能.标记('输出之前');
            输出解析器;
            写文件回调;
            输出仅声明文件;
            自定义转换器;

            /*

            常量 转换器 = 获取转换器组(自定义转换器);
            常量 输出结果 = 输出文件组(
                输出解析器,
                获取输出主机(写文件回调),
                源文件,
                输出仅声明文件,
                转换器);                
            */

            性能.标记('输出之后');
            性能.度量('输出', '输出之前', '输出之后');
            返回;
        }

        函数 获取源文件(文件名: 文字): 源文件_ {
            返回 获取源文件从路径(转为路径(文件名));
        }

        函数 获取源文件从路径(路径: 路径_): 源文件_ {
            返回 文件组从名称.获取(路径)!;
        }

        函数 获取诊断助手(
            源文件: 源文件_,
            获取诊断: (源文件: 源文件_, 取消令牌: 取消令牌_) => 只读数组_<诊断_>,
            取消令牌: 取消令牌_): 只读数组_<诊断_> {
            如果 (源文件) {
                返回 获取诊断(源文件, 取消令牌);
            }
            返回 排序和消除重复诊断信息(扁平词典(程序.获取源文件组(), 源文件 => {
                如果 (取消令牌) {
                    取消令牌.如果请求取消则抛出();
                }
                返回 获取诊断(源文件, 取消令牌);
            }));
        }

        函数 获取语法诊断信息(源文件: 源文件_, 取消令牌: 取消令牌_): 只读数组_<诊断_> {
            返回 获取诊断助手(源文件, 获取文件语法诊断, 取消令牌);
        }

        函数 获取语义诊断信息(源文件: 源文件_, 取消令牌: 取消令牌_): 只读数组_<诊断_> {
            返回 获取诊断助手(源文件, 获取文件语义诊断, 取消令牌);
        }

        函数 获取声明诊断信息(源文件: 源文件_, 取消令牌: 取消令牌_): 只读数组_<诊断_> {
            如果 (!源文件) {
                返回 获取文件的声明诊断(源文件, 取消令牌);
            }
            否则 {
                返回 获取诊断助手(源文件, 获取文件的声明诊断, 取消令牌);
            }
        }

        函数 获取文件语法诊断(源文件: 源文件_): 只读数组_<诊断_> {
            返回 源文件.解析诊断信息;
        }

        函数 运行包括取消令牌<T>(func: () => T): T {
            尝试 {
                返回 func();
            }
            捕获 (e) {
                如果 (e 身为 操作取消异常_) {
                    // We were canceled while performing the operation.  Because our 类型 checker
                    // might be a bad 状态, we need to throw it away.
                    //
                    // Note: we are overly aggressive here.  We do not actually *have* to throw away
                    // the 'noDiagnosticsTypeChecker'.  However, for simplicity, i'd like to keep
                    // the lifetimes of these two TypeCheckers the same.  Also, we generally only
                    // cancel when the user has made a change anyways.  And, in that case, we (the
                    // program instance) will get thrown away anyways.  So trying to keep one of
                    // these 类型 checkers alive doesn't serve much purpose.
                    无诊断类型检查器 = 未定!;
                    诊断生成类型检查器 = 未定!;
                }

                抛出 e;
            }
        }

        函数 获取文件语义诊断(源文件: 源文件_, 取消令牌: 取消令牌_): 诊断_[] {
            返回 获取和缓存诊断(源文件, 取消令牌, 缓存的文件的语言诊断, 获取文件无缓存的语义诊断);
        }

        函数 获取文件无缓存的语义诊断(源文件: 源文件_, 取消令牌: 取消令牌_): 诊断_[] {
            返回 运行包括取消令牌(() => {
                常量 类型检查 = 获取诊断生成类型检查器();
                调试_.断言(!!源文件.绑定诊断信息组);
                常量 包括绑定和检查诊断 = 源文件.脚本种类 === 脚本种类_.K;
                常量 绑定诊断信息组 = 包括绑定和检查诊断 ? 源文件.绑定诊断信息组 : 空数组;
                常量 检查诊断 = 包括绑定和检查诊断 ? 类型检查.获取诊断组(源文件, 取消令牌) : 空数组;
                常量 文件内的文件处理诊断 = 文件处理诊断.获取诊断(源文件.文件名);
                常量 文件中的程序诊断 = 程序诊断.获取诊断(源文件.文件名);
                返回 绑定诊断信息组.连接(检查诊断, 文件内的文件处理诊断, 文件中的程序诊断);
            });
        }

        函数 获取和缓存诊断(
            源文件: 源文件_ | 未定,
            取消令牌: 取消令牌_,
            缓存: 诊断缓存_,
            获取诊断: (源文件: 源文件_, 取消令牌: 取消令牌_) => 诊断_[]) {

            常量 缓存结果 = 源文件
                ? 缓存.每个文件 && 缓存.每个文件.获取(源文件.路径)
                : 缓存.全部诊断;

            如果 (缓存结果) {
                返回 缓存结果;
            }
            常量 结果 = 获取诊断(源文件!, 取消令牌) || 空数组;
            如果 (源文件) {
                如果 (!缓存.每个文件) {
                    缓存.每个文件 = 创建词典<诊断_[]>();
                }
                缓存.每个文件.设置(源文件.路径, 结果);
            }
            否则 {
                缓存.全部诊断 = 结果;
            }
            返回 结果;
        }

        函数 获取文件的声明诊断(源文件: 源文件_, 取消令牌: 取消令牌_): 诊断_[] {
            返回 源文件.是声明文件 ? [] : 获取文件的声明诊断(源文件, 取消令牌);
        }

        函数 获取选项诊断信息(): 诊断_[] {
            返回 排序和消除重复诊断信息(组合(
                文件处理诊断.获取全局诊断(),
                程序诊断.获取全局诊断(),
            ));
        }

        函数 获取全局诊断信息(): 诊断_[] {
            返回 排序和消除重复诊断信息(获取诊断生成类型检查器().获取全局诊断().分切());
        }

        函数 进程根文件(文件名: 文字, 是默认支持库: 真假) {
            进程源文件(正规化路径(文件名), 是默认支持库, /*包id*/ 未定);
        }

        函数 模块名称是相等(a: 文本字面量_, b: 文本字面量_): 真假 {
            返回 a.文本 === b.文本;
        }

        函数 收集外部模块引用(文件: 源文件_): 无值 {
            如果 (文件.导入组) {
                返回;
            }
            变量 导入组: 文本字面量_[] = [];
            循环 (变量 n 属于 文件.语句组) {
                假如 (n.种类) {
                    若是 语法_.导入声明_:
                        循环 (变量 目录 属于 (<导入声明_>n).导入组) {
                            附加如果唯一(导入组, 目录.模块名, 模块名称是相等)
                        }
                        跳出;
                    若是 语法_.导入从声明_:
                        附加如果唯一(导入组, (<导入从声明_>n).模块名, 模块名称是相等)
                        跳出;
                }
            }

            如果 (!导入组.长度) {
                导入组 = 未定!
            }
            文件.导入组 = 导入组 || 空数组;
            返回;
        }

        函数 获取源文件从引用平台(
            文件名: 文字,
            获取源文件: (文件名: 文字) => 源文件_ | 未定,
            失败?: (诊断: 诊断信息_, ...参数: 文字[]) => 无值,
            引用文件?: 源文件_): 源文件_ | 未定 {

            如果 (具有扩展(文件名)) {
                如果 (!循环执行(支持的扩展组, 扩展名 => 文件扩展是(主机!.获取规范文件名称(文件名), 扩展名))) {
                    如果 (失败) 失败(诊断信息_.File_0_has_unsupported_extension_The_only_supported_extensions_are_1, 文件名, '"' + 支持的扩展组.连结('", "') + '"');
                    返回 未定;
                }

                常量 源文件 = 获取源文件(文件名);
                如果 (失败) {
                    如果 (!源文件) {
                        失败(诊断信息_.没发现文件_0, 文件名);
                    }
                    否则 如果 (引用文件 && 主机!.获取规范文件名称(文件名) === 主机!.获取规范文件名称(引用文件.文件名)) {
                        失败(诊断信息_.A_file_cannot_have_a_reference_to_itself);
                    }
                }
                返回 源文件;
            }
            否则 {
                常量 源文件没扩展 = 获取源文件(文件名);
                如果 (源文件没扩展) 返回 源文件没扩展;

                如果 (失败) {
                    失败(诊断信息_.没发现文件_0, 文件名);
                    返回 未定;
                }

                常量 源文件添加扩展名 = 循环执行(支持的扩展组, 扩展名 => 获取源文件(文件名 + 扩展名));
                如果 (失败 && !源文件添加扩展名) {
                    失败!(诊断信息_.没发现文件_0, 文件名 + 扩展名_.K);
                }
                返回 源文件添加扩展名;
            }
        }

        /** This has side effects through `findSourceFile`. */
        函数 进程源文件(文件名: 文字, 是默认支持库: 真假, 引用文件?: 源文件_, 引用首位?: 数字, 引用尾位?: 数字): 无值 {
            获取源文件从引用平台(文件名,
                文件名 => 查找源文件(文件名, 转为路径(文件名), 是默认支持库, 引用文件!, 引用首位!, 引用尾位!, 模块导入位置种类_.无_),
                (诊断, ...实参组) => {
                    文件处理诊断.添加(引用文件 !== 未定 && 引用尾位 !== 未定 && 引用首位 !== 未定
                        ? 创建文件诊断信息(引用文件, 引用首位, 引用尾位 - 引用首位, 诊断, ...实参组)
                        : 创建编译器诊断(诊断, ...实参组));
                },
                引用文件);
        }

        函数 报告文件名称只是大小写不同(文件名: 文字, 现有文件名: 文字, 引用文件: 源文件_, 引用首位: 数字, 引用尾位: 数字): 无值 {
            如果 (引用文件 !== 未定 && 引用首位 !== 未定 && 引用尾位 !== 未定) {
                文件处理诊断.添加(创建文件诊断信息(引用文件, 引用首位, 引用尾位 - 引用首位,
                    诊断信息_.文件_0_与文件_1_只是名称大小写不同, 文件名, 现有文件名));
            }
            否则 {
                文件处理诊断.添加(创建编译器诊断(诊断信息_.文件_0_与文件_1_只是名称大小写不同, 文件名, 现有文件名));
            }
        }

        // Get source 文件 from normalized 文件名
        函数 查找源文件(文件名: 文字, 路径: 路径_, 是默认支持库: 真假, 引用文件: 源文件_, 引用首位: 数字, 引用尾位: 数字, 文件导入位置种类: 模块导入位置种类_): 源文件_ | 未定 {
            如果 (文件组从名称.具有(路径)) {
                常量 文件 = 文件组从名称.获取(路径);
                // try to check if we've already seen this 文件 but with a different casing in 路径
                // NOTE: this only makes sense for case-insensitive 文件 systems
                如果 (文件 && 获取标准化绝对路径(文件.文件名, 当前目录) !== 获取标准化绝对路径(文件名, 当前目录)) {
                    报告文件名称只是大小写不同(文件名, 文件.文件名, 引用文件, 引用首位, 引用尾位);
                }

                // If the 文件 was previously found via a node_modules search, but is now being processed as a root 文件,
                // then everything it sucks in may also be marked incorrectly, and needs to be checked again.
                如果 (文件 && 源文件是从外部模块组搜索的.获取(文件.路径)) {
                    源文件是从外部模块组搜索的.设置(文件.路径, 为假);
                    处理导入模块(文件);
                }
                文件!.文件导入位置 = 文件导入位置种类
                返回 文件;
            }

            // We haven't looked for this 文件, do so now and cache 结果
            常量 文件 = 主机!.获取源文件(文件名, 主机错误消息 => {
                如果 (引用文件 !== 未定 && 引用首位 !== 未定 && 引用尾位 !== 未定) {
                    文件处理诊断.添加(创建文件诊断信息(引用文件, 引用首位, 引用尾位 - 引用首位,
                        诊断信息_.无法读取文件_0_Colon_1, 文件名, 主机错误消息));
                }
                否则 {
                    文件处理诊断.添加(创建编译器诊断(诊断信息_.无法读取文件_0_Colon_1, 文件名, 主机错误消息));
                }
            }, 应创建新源文件);

            文件组从名称.设置(路径, 文件);
            如果 (文件) {
                源文件是从外部模块组搜索的.设置(路径, 文件导入位置种类 === 模块导入位置种类_.全局支持库_ || 文件导入位置种类 === 模块导入位置种类_.外部支持库_);
                文件.路径 = 路径;

                如果 (主机!.使用区分大小写文件名()) {
                    常量 路径转为小写 = 路径.转为小写();
                    // for case-sensitive 文件 systems check if we've already seen some 文件 with similar filename ignoring case
                    常量 现有文件 = 按名称排列忽略大小写!.获取(路径转为小写);
                    如果 (现有文件) {
                        报告文件名称只是大小写不同(文件名, 现有文件.文件名, 引用文件, 引用首位, 引用尾位);
                    }
                    否则 {
                        按名称排列忽略大小写!.设置(路径转为小写, 文件);
                    }
                }

                // always process imported modules to record module 名称 resolutions
                处理导入模块(文件);

                如果 (是默认支持库) {
                    文件.是默认支持库 = 为真
                    文件.文件导入位置 = 模块导入位置种类_.全局支持库_
                    文件组.推入(文件);
                }
                否则 {
                    文件.文件导入位置 = 文件导入位置种类
                    文件组.压入(文件);
                }
            }
            返回 文件;
        }

        函数 获取规范文件名称(文件名: 文字): 文字 {
            返回 主机!.获取规范文件名称(文件名);
        }

        函数 处理导入模块(文件: 源文件_) {
            收集外部模块引用(文件);
            如果 (文件.导入组.长度) {
                常量 模块名称 = 获取模块名称(文件);
                常量 旧程序状态: 旧程序状态_ = { 程序: 旧程序, 旧源文件: 旧程序 && 旧程序.获取源文件(文件.文件名), 修改的文件路径组: 修改的文件路径组 };
                常量 决议组 = 解析模块名称重用旧状态(模块名称, 获取标准化绝对路径(文件.文件名, 当前目录)!, 文件, 旧程序状态);
                调试_.断言(决议组.长度 === 模块名称.长度);
                循环 (变量 i = 0; i < 模块名称.长度; i++) {
                    常量 解析的 = 决议组[i];
                    设置解析模块(文件, 模块名称[i], 解析的);
                    如果 (!解析的) {
                        继续;
                    }

                    常量 文件名称组 = 过滤器(主机!.读目录(解析的.解析的模块目录路径, [扩展名_.K], 1), 扩展名是K和DK不含TestK)

                    循环 (常量 解析的文件名 属于 文件名称组) {
                        常量 应添添加文件 = 解析的文件名 && !获取解析诊断(解析的) && i < 文件.导入组.长度
                        如果 (应添添加文件) {
                            常量 路径 = 转为路径(解析的文件名);
                            常量 首位 = 跳过杂项(文件.文本, 文件.导入组[i].首位);
                            常量 源文件 = 查找源文件(解析的文件名, 路径, /*isDefaultLib*/ 为假, 文件, 首位, 文件.导入组[i].尾位, 解析的.是外部支持库导入!);
                            如果 (源文件) {
                                (解析的.全部源码文件名 || ( 解析的.全部源码文件名 = [])).压入( 源文件.文件名)
                            }
                        }
                    }
                }
            }
            否则 {
                // no imports - drop cached module resolutions
                文件.解析的模块 = 未定!;
            }
        }

        函数 计算公共源目录(源文件组: 源文件_[]): 文字 {
            常量 文件名称组: 文字[] = [];
            循环 (常量 文件 属于 源文件组) {
                如果 (!文件.是声明文件) {
                    文件名称组.压入(文件.文件名);
                }
            }
            返回 计算文件名从公共路径(文件名称组, 当前目录, 获取规范文件名称);
        }

        函数 是输出的文件(文件: 文字) {
            常量 文件路径 = 转为路径(文件);
            如果 (获取源文件从路径(文件路径)) {
                返回 为假;
            }
            返回 为假;
        }
    }

    /* @internal */
    /**
     * Returns a 诊断信息_ if we won't include a resolved module due to its extension.
     * The 诊断信息_'s 参数 are the imported module 名称, and the filename it resolved to.
     * This returns a diagnostic even if the module will be an untyped module.
     */
    导出 函数 获取解析诊断(_: 解析的模块名称_): 诊断信息_ | 未定 {
        返回 未定
    }

    函数 检查全部定义(名称组: 文字[]): 文字[] {
        调试_.断言(名称组.每个(名称 => 名称 !== 未定), '名称是未定义的.', () => JSON.序列化(名称组));
        返回 名称组;
    }

    函数 获取模块名称(源文件: 源文件_): 文字[] {
        返回 源文件.导入组.映射(i => 路径以分隔符结尾(i.文本) ? i.文本!.分切(0, -1) : i.文本);
    }
}
